#include <cstdio>
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;

#include <string>
using std::string;

#include <stdexcept>
using std::runtime_error;

#include "cryptopp/osrng.h"
using CryptoPP::AutoSeededRandomPool;

#include "cryptopp/fhmqv.h"
#include "cryptopp/eccrypto.h"
using CryptoPP::ECP;
using CryptoPP::FHMQV;

#include "cryptopp/secblock.h"
using CryptoPP::SecByteBlock;

// ASN1 is a namespace, not an object
#include "cryptopp/asn.h"
#include "cryptopp/oids.h"
using CryptoPP::OID;
using namespace CryptoPP::ASN1;

#include "cryptopp/integer.h"
using CryptoPP::Integer;

#include "cryptopp/sha.h"
using CryptoPP::SHA256;

#include "cryptopp/gfpcrypt.h"

#include "cryptopp/hex.h"
using CryptoPP::HexEncoder;

#include "cryptopp/files.h"
using CryptoPP::FileSource;
using CryptoPP::FileSink;

#include <time.h>
#include <iomanip>
#include <sstream>

using namespace std;
using namespace CryptoPP;
#include <assert.h>

const double CLOCK_TICKS_PER_SECOND = (double)CLOCKS_PER_SEC;
AutoSeededRandomPool globalRNG;

void outputResultOperations(const string& name, const string& operation, int iterations, double timeTaken)
{
    cout << "\n<TR><TH>" << name << " " << operation;
    cout << "<TD>" << iterations;
    cout << "<TD>" << setprecision(5) << setiosflags(ios::fixed) << ((1000 * timeTaken) / iterations);
    cout << resetiosflags(ios::fixed);
}


void dumpGroupParameters(const char* filename, const DL_GroupParameters_IntegerBased& groupParams)
{
    HexEncoder encoder(new FileSink(filename, false));
    groupParams.DEREncode(encoder);
    encoder.MessageEnd();
}


/* MQV *******************************************************************************************************/


void benchmarkMQVKeyPairGeneration(const string& mqvName, const MQV& mqv, int iterations)
{
    SecByteBlock eprivA(mqv.EphemeralPrivateKeyLength()), epubA(mqv.EphemeralPublicKeyLength());

    clock_t t = clock();

    for(int i = 0; i < iterations; i++) {
        mqv.GenerateEphemeralKeyPair(globalRNG, eprivA, epubA);
    }

    double timeTaken = (clock() - t) / CLOCK_TICKS_PER_SECOND;
    outputResultOperations(mqvName, "KeyPairGeneration", iterations, timeTaken);
}

void benchmarkMQVKeyAgreement(const string& mqvName, const MQV& mqv, int iterations)
{
    SecByteBlock eprivA(mqv.EphemeralPrivateKeyLength()), epubA(mqv.EphemeralPublicKeyLength());
    SecByteBlock sprivA(mqv.StaticPrivateKeyLength()), spubA(mqv.EphemeralPublicKeyLength());
    SecByteBlock eprivB(mqv.EphemeralPrivateKeyLength()), epubB(mqv.EphemeralPublicKeyLength());
    SecByteBlock sprivB(mqv.StaticPrivateKeyLength()), spubB(mqv.EphemeralPublicKeyLength());

    mqv.GenerateStaticKeyPair(globalRNG, sprivA, spubA);
    mqv.GenerateStaticKeyPair(globalRNG, sprivB, spubB);
    mqv.GenerateEphemeralKeyPair(globalRNG, eprivA, epubA);
    mqv.GenerateEphemeralKeyPair(globalRNG, eprivB, epubB);

    SecByteBlock shared(mqv.AgreedValueLength());

    clock_t t = clock();

    for(int i = 0; i < iterations; i+=2) {
        mqv.Agree(shared, sprivA, eprivA, spubB, epubB);
        mqv.Agree(shared, sprivB, eprivB, spubA, epubA);
    }

    double timeTaken = (clock() - t) / CLOCK_TICKS_PER_SECOND;
    outputResultOperations(mqvName, "KeyAgreement", iterations, timeTaken);
}

void benchmarkMQV(int pbits, int iterations)
{
    cout << "<TABLE border=1>" << endl;
    cout << "<THEAD><TR><TH>Operation<TH>Iterations<TH>Avg-Time(ms)" << endl;
    cout << "<TBODY style=\"background: yellow\">" << endl;

    stringstream ss;
    ss << "MQV-" << pbits;
    string mqvName = ss.str();

    DL_GroupParameters_GFP_DefaultSafePrime gfp_params;
    gfp_params.Initialize(globalRNG, pbits);
    MQV mqv(gfp_params);

    //FileSource f("TestData/mqv1024.dat", true, new HexDecoder());
    //MQV mqv(f);

    //benchmark individual operations
    benchmarkMQVKeyPairGeneration(mqvName, mqv, iterations);
    benchmarkMQVKeyAgreement(mqvName, mqv, iterations);

    cout << endl;
}

void benchmarkMQVAll()
{
    benchmarkMQV(2048, 5000);
    benchmarkMQV(3072, 5000);
    benchmarkMQV(7680, 5000);
}

/************************************************************************************************************/


int main(int argc, char* argv[])
{
    benchmarkMQVAll();
}
